package club.jdiy.core.base;

import club.jdiy.core.base.domain.MongoEntity;
import club.jdiy.core.base.domain.Pager;
import club.jdiy.core.base.domain.Removable;
import club.jdiy.core.base.domain.Sortable;
import club.jdiy.utils.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import javax.annotation.Resource;
import javax.persistence.NoResultException;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Optional;

@SuppressWarnings("ALL")
/**
 * MongoDB 的DAO层抽象实现.
 *
 * @param <T>  实体类
 * @param <PK> 实体主键类型
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20200519
 */
public abstract class MongoDao<T extends MongoEntity<PK>, PK> implements JpaDyn<T, PK> {
    private Class<T> tClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

    @Resource(name = "mongoTemplate")
    protected MongoTemplate dbo;

    /**
     * 查询条件统一处理方法.
     * <p>
     * 若实体的Dao/Service层使用了如下方法，则应当在Dao实现类中重写此方法，以便处理{@link T}查询条件。
     * <ul>
     *     <li>{@link #count(MongoEntity)}</li>
     *     <li>{@link #findList(int, MongoEntity)}</li>
     *     <li>{@link #findList(int, MongoEntity, String, String)}</li>
     *     <li>{@link #findPager(int, int, MongoEntity, String, String)}</li>
     *     <li>{@link #findPager(int, int, MongoEntity)}</li>
     * </ul>
     *
     * @param t     查询条件
     * @param order 排序字段
     * @param sort  排序方式: asc 或 desc
     * @return Filter
     */
    protected Filter createFilter(T t, String order, String sort) {
        Query query = new Query();
        if (Removable.class.isAssignableFrom(tClass)) query.addCriteria(new Criteria("removed").is(false));
        return new MongoDao.Filter(query, order, sort);
    }

    @Override
    public void delete(T t) {
        this.dbo.remove(t);
    }

    @Override
    public <S extends T> S save(S t) {
        this.dbo.save(t);
        return t;
    }

    @Override
    public Optional<T> findById(PK id) {
        if (StringUtils.isEmpty(id) || "0".equals(id) || id.equals(0))
            return Optional.empty();//空id, 0 id(一般是添加实体时，查询的情况), 都返回null
        return Optional.ofNullable(this.dbo.findOne(new Query().addCriteria(new Criteria("_id").is(id)), tClass));
    }

    @Override
    public long count(T t) {
        try {
            MongoDao<T, PK>.Filter filter = this.createFilter(t, (String) null, (String) null);
            return ((Long) this.dbo.count(filter.getQuery(), tClass));
        } catch (NoResultException ignore) {
            return 0L;
        }
    }

    public List<T> findList(int size, T t) {
        return findList(size, t, null, null);
    }
    @Override
    public List<T> findList(int size, T t, String order, String sort) {
        return createPager(size, 1, createFilter(t, order, sort), false).getItems();
    }

    @Override
    public Pager<T> findPager(int pageSize, int page, T t, String order, String sort) {
        return createPager(pageSize, page, createFilter(t, order, sort), true);
    }
    public Pager<T> findPager(int pageSize, int page, T t) {
        return findPager(pageSize, page, t, null, null);
    }

    private final Pager<T> createPager(int pageSize, int page, Filter filter, boolean countTotal) {
        int rowCount = -1;
        if (pageSize > 0) {
            if (countTotal) {
                try {
                    rowCount = ((Long) this.dbo.count(filter.getQuery(), tClass)).intValue();
                } catch (NoResultException ignore) {
                    rowCount = 0;
                }
            }
            if (page < 1) page = 1;
            filter.getQuery().skip(pageSize * (page - 1)).limit(pageSize);
        }

        //排序特性放到dbo.count方法之后，以免影响count查询效率。
        if (filter.getOrder() != null && !"".equals(filter.getOrder().trim()))
            filter.getQuery().with(
                    "desc".equalsIgnoreCase(filter.getSort())
                            ? Sort.by(Sort.Order.desc(filter.getOrder()))
                            : Sort.by(Sort.Order.asc(filter.getOrder()))
            );
        else if (Sortable.class.isAssignableFrom(tClass))
            filter.getQuery().with(Sort.by(Sort.Order.asc("sortIndex")));
        else filter.getQuery().with(Sort.by(Sort.Order.asc("_id")));

        List<T> items = this.dbo.find(filter.getQuery(), tClass);
        if (rowCount == -1) rowCount = items.size();
        return new Pager<>(page, pageSize, rowCount, items);
    }

    protected class Filter {
        private Query query;
        private String order;
        private String sort;

        public Filter(Query query, String order, String sort) {
            this.query = query;
            this.order = order;
            this.sort = sort;
        }

        public Query getQuery() {
            return query;
        }

        public String getOrder() {
            return order;
        }

        public String getSort() {
            return sort;
        }
    }
}
